SpingBoot防止重复提交请求 您所在的位置:网站首页 spring boot请求接口 SpingBoot防止重复提交请求

SpingBoot防止重复提交请求

2023-06-13 03:20| 来源: 网络整理| 查看: 265

注解+过滤器+拦截器+redis缓存(若依框架) 实现思路:

1.首先过滤器过滤http请求,重新组装为可重复读取的request流(由于需要从request流中读取body数据,而request流不能重复读取,所以需要创建一个可重复读取的流)

2.拦截器拦截到注解标记的指定方法,获取方法请求url以及请求头组成一个缓存键,将请求时间和请求参数放到一个map中作为缓存值。

3.根据缓存键获取缓存中对象,如果存在,判断当前请求参数和上次请求参数是否相同,以及当前请求时间和上次请求时间相差是否在指定范围内,根据规则判断是否重复提交,如果是重复提交,直接返回错误信息。

4.如果不是重复提交,添加缓存键以及值到redis内存当中,用于下一次校验重复提交。

具体实现:

1.创建几个工具类

redis工具类:用于设置和获取缓存

/** * spring redis 工具类 * **/ @Component public class RedisCache { @Resource public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public T getCacheObject(final String key) { ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } }

HTTP封装工具类:用于获取流中body数据 

/** * 通用http工具封装工具 * */ public class HttpHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try (InputStream inputStream = request.getInputStream()) { reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { LOGGER.warn("getBodyString出现问题!"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { LOGGER.error(ExceptionUtils.getMessage(e)); } } } return sb.toString(); } }

2.创建一个方法注解,用于拦截指定方法

@Inherited @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * 间隔时间(ms),小于此时间视为重复提交 */ int interval() default 5000; /** * 提示消息 */ String message() default "不允许重复提交,请稍候再试"; }

3.创建可重复读取流的包装类

public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { super(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //获取请求体 body = HttpHelper.getBodyString(request).getBytes(StandardCharsets.UTF_8); } @Override public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() { final ByteArrayInputStream basis = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() { return basis.read(); } @Override public int available() { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }

4.创建过滤器,重新包装为可重复读取的request流,方便后续拦截器能读取流中的body数据

public class RepeatableFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { //包装请求,构建新的可重复读的request流 requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); } if (null == requestWrapper) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }

5.创建拦截器,拦截注解标记的方法,验证是否重复提交

@Component public abstract class RepeatSubmitInterceptor implements HandlerInterceptor { /** * 拦截注解方法 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //获取方法注解 RepeatSubmit methodAnnotation = method.getAnnotation(RepeatSubmit.class); //获取类注解 RepeatSubmit classAnnotation = method.getDeclaringClass().getAnnotation(RepeatSubmit.class); //优先取方法注解参数 RepeatSubmit repeatSubmit = !ObjectUtils.isEmpty(methodAnnotation) ? methodAnnotation : classAnnotation; //拦截 if (repeatSubmit != null && (this.isRepeatSubmit(request, repeatSubmit))) { String message = repeatSubmissage(); try { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(message); } catch (IOException e) { e.printStackTrace(); } return false; } //放行 return true; } else { return true; } } /** * 验证是否重复提交由子类实现具体的防重复提交的规则 */ public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); }

6.创建拦截器子类,由子类实现验证重复提交规则

@Component public class SameUrlDataInterceptor extends RepeatSubmitInterceptor { public static final String REPEAT_PARAMS = "repeatParams"; public static final String REPEAT_TIME = "repeatTime"; // 令牌自定义标识 private static final String HEADER = "Authorization"; @Resource private RedisCache redisCache; @Override public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { String nowParams = ""; //获取可重复读取的request流,并从流中获取body if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; //获取body nowParams = HttpHelper.getBodyString(repeatedlyRequest); } // body参数为空,获取Parameter的数据 if (StringUtils.isEmpty(nowParams)) { nowParams = JSON.toJSONString(request.getParameterMap()); } Map nowDataMap = new HashMap(); //设置请求参数 nowDataMap.put(REPEAT_PARAMS, nowParams); //设置请求时间 nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 请求地址(作为存放cache的key值) String url = request.getRequestURI(); // 唯一值(没有消息头则使用请求地址) String submitKey = StringUtils.trimToEmpty(request.getHeader(HEADER)); // 唯一标识(指定key + url + 消息头) String cacheRepeatKey = "repeat_submit:" + url + submitKey; //获取缓存对象 Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); Map sessionMap; if (sessionObj != null) { sessionMap = (Map) sessionObj; //比较请求参数以及请求时间,为true代表短时间内多次重复提交 if (compareParams(nowDataMap, sessionMap) && compareTime(nowDataMap, sessionMap, annotation.interval())) { return true; } } //如果不是短时间内重复提交则添加或更新缓存对象 redisCache.setCacheObject(cacheRepeatKey, nowDataMap, annotation.interval(), TimeUnit.MILLISECONDS); return false; } /** * 判断参数是否相同 */ private boolean compareParams(Map nowMap, Map preMap) { String nowParams = (String) nowMap.get(REPEAT_PARAMS); String preParams = (String) preMap.get(REPEAT_PARAMS); return nowParams.equals(preParams); } /** * 判断两次间隔时间 */ private boolean compareTime(Map nowMap, Map preMap, int interval) { long time1 = (Long) nowMap.get(REPEAT_TIME); long time2 = (Long) preMap.get(REPEAT_TIME); return (time1 - time2) < interval; } }

7.创建过滤器配置类将过滤器注册到过滤器链中

public class FilterConfig { /** * 防止重复提交过滤器 */ @SuppressWarnings({"rawtypes", "unchecked"}) @Bean public FilterRegistrationBean someFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RepeatableFilter()); registration.addUrlPatterns("/*"); registration.setName("repeatableFilter"); //优先级为最低 registration.setOrder(Ordered.LOWEST_PRECEDENCE); return registration; } }

8.创建拦截器配置类将拦截器注册到容器当中

@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; /** * 自定义拦截规则 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } }

9.创建reids配置类

@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @SuppressWarnings(value = {"unchecked", "rawtypes"}) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } static class FastJson2JsonRedisSerializer implements RedisSerializer { public final Charset defaultCharset = StandardCharsets.UTF_8; private final Class clazz; public FastJson2JsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(defaultCharset); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有